#include "llvm/Support/CommandLine.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/ASTConsumers.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/TargetOptions.h"
#include "llvm/Support/Host.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/PreprocessorOptions.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Lex/HeaderSearch.h"
#include "clang/AST/ASTConsumer.h"
#include "llvm/Support/raw_ostream.h"
#include "clang/Parse/ParseAST.h"
#include "clang/AST/ASTContext.h"

#include <iostream>

int file_size(char* filename)
{
	FILE *fp=fopen(filename,"r");
	if(!fp) return -1;
	fseek(fp,0L,SEEK_END);
	int size=ftell(fp);
	fclose(fp);

	return size;
}

static llvm::cl::opt<std::string> FileName(llvm::cl::Positional, llvm::cl::desc("Input file"), llvm::cl::Required);
int main(int argc, char **argv)
{
	llvm::cl::ParseCommandLineOptions(argc, argv, "preprocess test\n");

	llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> p_diag_option(new clang::DiagnosticOptions());
	clang::TextDiagnosticPrinter* p_diag_printer = new clang::TextDiagnosticPrinter(llvm::outs(), p_diag_option.get());
	llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> p_diag_ids(new clang::DiagnosticIDs());

	clang::DiagnosticsEngine* pDiagnosticsEngine = new clang::DiagnosticsEngine(p_diag_ids,
			p_diag_option,
			p_diag_printer);

	clang::LangOptions languageOptions;

	clang::CompilerInstance ci;

	// ci.createDiagnostics();
	ci.setDiagnostics(pDiagnosticsEngine);
	std::shared_ptr<clang::TargetOptions> pto = std::make_shared<clang::TargetOptions>();
	pto->Triple = llvm::sys::getDefaultTargetTriple();
	clang::TargetInfo *pti= clang::TargetInfo::CreateTargetInfo(ci.getDiagnostics(), pto);
	ci.setTarget(pti);

	ci.createFileManager();
	ci.createSourceManager(ci.getFileManager());
	ci.createPreprocessor(clang::TU_Complete);

	std::shared_ptr<clang::TargetOptions> targetOptions = std::make_shared<clang::TargetOptions>();
	targetOptions->Triple = llvm::sys::getDefaultTargetTriple();

	clang::TargetInfo *pTargetInfo =
		clang::TargetInfo::CreateTargetInfo(
				*pDiagnosticsEngine,
				targetOptions);

	const clang::FileEntry *file = ci.getFileManager().getVirtualFile(FileName, file_size(argv[1]), 0);
	clang::FileID mainFileID = ci.getSourceManager().createFileID(file, clang::SourceLocation(), clang::SrcMgr::C_User);
	ci.getSourceManager().setMainFileID(mainFileID);

	// add search header directory
	std::vector<clang::DirectoryLookup> lookups;
	for (const std::string& path : std::vector<std::string> {
			"/usr/include",
			"/usr/include/x86_64-linux-gnu",
			"/usr/lib/gcc/x86_64-linux-gnu/9/include"
			}) {
		llvm::Expected<clang::DirectoryEntryRef> dirRef = ci.getFileManager().getDirectoryRef(path);
		std::cout << path << std::endl;
		clang::DirectoryEntryRef ref = dirRef.get();
		auto lookup = clang::DirectoryLookup(ref, clang::SrcMgr::CharacteristicKind::C_User,false);
		if (!lookup.getDir())
			std::cout << "Cannot lookup user defined dir" << std::endl;
		lookups.emplace_back(lookup);
	}
	ci.getPreprocessor().getHeaderSearchInfo().SetSearchPaths(lookups, 0, 0, false);

	ci.getPreprocessor().EnterMainSourceFile();

	p_diag_printer->BeginSourceFile(languageOptions, &ci.getPreprocessor());
	clang::Token token;
	do {
		ci.getPreprocessor().Lex(token);
		if(pDiagnosticsEngine->hasErrorOccurred())
		{
			break;
		}
		ci.getPreprocessor().DumpToken(token);
		std::cerr << std::endl;
	} while(token.isNot(clang::tok::eof));
	p_diag_printer->EndSourceFile();

	p_diag_option.reset();

	return 0;
}
